PreVibeRange · system map · v0 draft · 2026.04.20

A small stack for UI code that can be proven, not just shipped.

Three primitives that stack. pretext measures text without touching the DOM. vibescript runs one render loop with one state object. freerange proves layout facts from source, no browser. Together: UI code that is deterministic, inspectable, and statically defensible — the floor AI-generated interfaces have been missing.

3 primitives 1 render loop 0 browsers in the proof path refs · References.html
01 — The Stack

Three layers. Each one removes a tax the web has quietly been paying.

Layer · measurement

pretext

Turn any paragraph into pure numbers — height, line count, widest line — without a single DOM read.

Uses the browser's font engine as ground truth once, then all further answers are arithmetic over cached widths. No getBoundingClientRect, no reflow, no surprises at paint time.

Answers
height of this paragraph at width W widest line — the shrink‑wrap line ranges for virtualized text ahead‑of‑time overflow checks
Layer · architecture

vibescript

One render loop. One state object. Reads batched, writes batched, events stored as transient state.

Game‑engine discipline for UI: inputs → layout → animation → commit → DOM writes. Animations have closed‑form solutions where possible. Depth has a single Z.ts. First paint uses the same data flow as every other frame.

Answers
when does what run who owns this piece of state what is the one source of truth how do two gestures compose
Layer · proof

freerange

Layout facts stated as @fit comments and proven from source. No browser, no fixtures, no sampled cases.

Preserved length, non‑negative sizes, monotone row tops, bounded column counts. The boring facts that catch real agent mistakes — stated once above the helper, earned once from the source, consumed at every call site.

Answers
does rows.length == items.length always hold can the column count ever reach 8 is width ever negative here will this caption overflow at 320px
02 — Data Flow

How a single frame moves through the stack.

One frame

Inputs · once per frame

transientst.events.click
transientst.events.pointerDown
readwindowWidth · scrollY
persistentst.selectedId · st.springs

Layout · pure, provable

pretextprepare(text, font) · layout(prepared, w, lh)
@fitgridLayout(items, w) → cols: int 1..7
@fitstackLayout(rowSizes, gap) → nondecreasing tops
@fithitBoxes(rows) → one per row
animationspringStep(s) · spring sampled in closed form

Commit · one place each

stateObject.assign(st, next)
resetst.events.click = null
DOM writestransforms · cursor · visibility · scrollTo
side effectspushState · focus · localStorage
scheduleif (stillAnimating) scheduleRender()

Reads and writes are segregated. Every pure helper in the middle column has a contract the source earned — so whether the frame is frame 0 or frame 10,000, the shape of the answer is the same thing the contract promised. No DOM reads during layout. No DOM writes before commit. Events are raw state, interpreted centrally.

03 — In Code

One helper, all three layers visible.

Example

A caption sized by its own content. Pretext measures it. Freerange proves the result fits. Vibescript consumes the numbers during the layout phase of its render loop. No single layer is doing the other's job.

// layout/caption.ts
import { prepareWithSegments, layoutWithLines } from '@chenglou/pretext'

/** @fit
 * given maxWidth: 120..800
 * given lineHeight: 16..40
 * result.height >= 0
 * result.height <= maxWidth * 10
 * result.widestLine <= maxWidth
 * result.lines[].width <= maxWidth
 * result.lines.length == result.lineCount
 */
export function measureCaption(text: string, font: string, maxWidth: number, lineHeight: number) {
  const prepared = prepareWithSegments(text, font)
  const { lines } = layoutWithLines(prepared, maxWidth, lineHeight)

  let widest = 0
  for (const line of lines) if (line.width > widest) widest = line.width

  return {
    height: lines.length * lineHeight,
    lineCount: lines.length,
    widestLine: widest,
    lines,
  }
}

caption.ts · 22 lines · one pure helper, one contract, three layers

The contract is small. The source earns it. The call sites stop guessing.

Now anywhere that calls measureCaption — the grid, the focused view, the overlay, a print layout — knows by construction that the caption fits its width and that the returned lines array length matches lineCount. No caller has to rediscover that fact with a console log.

04 — Laws

Seven rules the stack enforces — softly, by making the right thing the easy thing.

Laws · 01‑07
01

Model geometry is render geometry.

If pretext says the caption fits in one line at width W, the painted DOM renders at width W too. "Measure against A, render at B" is a bug smell — assume so until proven otherwise.

02

Base geometry first, animated projection second.

Stable x/y/width/height is the source of truth. Animation is a visual residual on top. Hit testing, occlusion, scroll anchoring all read the base box, never the animated one.

03

DOM writes live in one phase.

Transforms, cursor, visibility, scrollTo, focus — all in the commit phase, at the end of render. Touching body.style.cursor during layout is still a DOM write.

04

Events are transient state, not logic.

Callbacks store the raw event and schedule a render. Nothing else. The render frame composes click + key + pointer together, so conflicting gestures resolve in one place.

05

Put facts on pure helpers, not pages.

Layout math belongs in @fit contracts on small helpers. Browser runs are for browser‑owned behavior: native selection, scroll physics, caret placement. Don't conflate them.

06

Assert the fragile interaction directly.

Not "the happy path works". Name the bug: clicking a caption selects the full prompt and stays in grid mode. Snapshot the thing a user would notice — ordered ids, visible row range, line count.

07

The check earns its size.

Small fixtures + one broader pass compressed away + one sensitivity check. "Tests pass" is too weak a stopping condition; "the check notices a meaningful regression" is the bar.

05 — Build Plan

Demos, chosen by the fragile interaction they expose — not by feature parity.

Demos · 01‑06
#
Demo
Fragile thing
The check that earns it
01
Photo gallery
grid ↔ line mode
Child caption rect during the 2D→1D handoff — the classic "base is right, child box teleported" regression.
@fitpretextframegrid cols: int 1..7; transition‑frame snapshot at t=16ms
02
Comment thread
pretext‑owned text
Height drifts when a last word wraps that "shouldn't". The measure‑vs‑render mismatch caught red‑handed.
pretext@fitwidestRenderedLine ≤ commentInnerWidth
03
Virtualized feed
variable‑height rows
The anchor row's viewport y after resize — starts correct, ends correct, dives in the middle.
trajectorysample t=0 · t=16ms · t=64ms · settled
04
Draggable cards
reorder + settle
The released card stays above the stack until settle — otherwise it pops through siblings mid‑spring.
snapshot@fitorderedIds + reorder‑threshold test
05
Typeahead
hit geometry per row
One hit box per visible row — exactly. Not one extra, not one missing, even mid‑virtualization.
@fithitBoxes.length == visibleRows.length
06
Responsive article
320..2000 px
Line count never increases when width increases. The monotonicity law of text layout.
pretext@fitsweep w=320..2000 step=20